package com.marion.mrpc.client;

import com.marion.mrpc.Request;
import com.marion.mrpc.Response;
import com.marion.mrpc.ServiceDescriptor;
import com.marion.mrpc.codec.*;
import com.marion.mrpc.transport.HttpTransportClient;
import com.marion.mrpc.transport.TransportClient;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

@Slf4j
public class RemoteInvoker implements InvocationHandler {

    private Class clazz;
    private Encoder encoder;
    private Decoder decoder;
    private TransportSelector selector;

    public <T> RemoteInvoker(Class<T> clazz, Encoder encoder, Decoder decoder, TransportSelector selector) {
        this.clazz = clazz;
        this.encoder = encoder;
        this.decoder = decoder;
        this.selector = selector;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) {

        /**
         * 1. 构建Request对象
         * 2. 调用远程服务
         * 3. 处理返回结果
         */

        Request request = new Request();
        request.setServiceDescriptor(ServiceDescriptor.from(clazz, method));
        request.setParameters(args);

        Response response = invokeRemote(request);
        if (response == null || response.getCode() != 0) {
            throw new IllegalStateException("fail invoke remote " + response);
        }

        return response.getData();
    }

    private Response invokeRemote(Request request) {

        /**
         * 1. 获得一个选择器selector
         * 2. 编码请求
         * 3. 调用远程服务
         * 4. 获得返回
         * 5. 解码结果
         * 6. 处理响应
         */
        Response response = null;
        TransportClient client = null;
        try {
            client = selector.select();

            byte[] encode = encoder.encode(request);
            InputStream write = client.write(new ByteArrayInputStream(encode));

            byte[] bytes = IOUtils.readFully(write, write.available());
            response = decoder.decode(bytes, Response.class);
        } catch (Exception e) {
            log.warn("[invokeRemote] e={}, {}", e.getMessage(), e);
            response = new Response();
            response.setCode(1);
            response.setMessage("Rpc error" + e.getClass() + ":" + e.getMessage());
        } finally {
            if (client != null) {
                selector.release(client);
            }
        }

        return response;
    }
}
